package com.chang.diy.bean;

import com.chang.diy.anno.Bean;
import com.chang.diy.anno.DI;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class AnnotationApplicationContext implements ApplicationContext {

    static String rootPath; // 初始化路径
    // 创建map集合，放bean对象
    private static Map<Class, Object> beanFactory = new HashMap<>();

    // 创建有参构造，传递包路径，设置包扫描规则
    // 当前包以及子包，哪一个类有@Bean注解，就把这个类通过反射实例化创建出来
    public AnnotationApplicationContext(String basePackage) throws IOException, ClassNotFoundException, InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException {

        // 1,把。替换为\
        String packagePath = basePackage.replaceAll("\\.", "\\\\");
        System.out.println("packagePath:>>>>>>>>" + packagePath);
        // 2,通过线程获取包绝对路径
        Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources(packagePath);
        System.out.println("urls:>>>>>>>>" + urls);
        // 遍历枚举路径
        while (urls.hasMoreElements()) {
            URL url = urls.nextElement();
            String filePath = URLDecoder.decode(url.getFile(), "utf-8");
            System.out.println("filePath:>>>>>>>>" + filePath);
            // 注意,windows系统文件路径不区分正反斜杠 \  / 作用都一样

            // 获取包前面路径部分，字符串截取  /D:/IDEA/IDEAproject/Spring6/spring_05_ioc_DIY/target/classes/
            rootPath = filePath.substring(0, filePath.length() - packagePath.length());
            System.out.println("rootPath:>>>>>>" + rootPath);

            // 包扫描
            loadBean(new File(filePath));
            //注意这边的思路，目的是获取项目程序所在文件目录，然后loadBean迭代检查目录里面编译过后的.class文件是否带有对应注解

        }
        loadDI(); // 属性注入
    }

    // 包扫描过程
    private static void loadBean(File file) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {

        // 1,判断当前是否为文件夹
        if (file.isDirectory()) {
            // 2,获取文件夹种所有内容
            File[] childrenFiles = file.listFiles();
            // 3,判断文件夹是否为空,
            if (childrenFiles == null || childrenFiles.length == 0) {
                return;
            }
            // 4,如果文件夹种不为空,遍历得到每个file对象,继续判断,如果还是文件夹就继续遍历 递归
            for (File children : childrenFiles) {
                if (children.isDirectory()) {
                    // 递归
                    loadBean(children);
                } else {  // 4.2遍历得到file对象不是文件夹,是文件,
                    // 4.3得到包路径+类名称部分   文件包路径com.chang.aaa.class = 文件绝对路径 -  包前面路径  rootPath
                    // System.out.println(rootPath);
                    String packagePath = children.getAbsolutePath().substring(rootPath.length() - 1);

                    // 4.4判断当前文件类型是否为.class类型
                    if (packagePath.contains(".class")) { // 判断路径字符串是否包含.class

                        // 4.5如果时.class类型,把路径\替换为.  再把.class去掉  最终得到类的完整路径  com.chang.aaa
                        String finalPath = packagePath.replaceAll("\\\\", ".")
                                .replace(".class", "");

                        // 4.6判断类上面是否有注解@Bean，如果有就实例化之后放到map集合beanFactory中
                        // 获取类的class对象
                        Class<?> clazz = Class.forName(finalPath);

                        if (!clazz.isInterface()) {  // 判断是不是接口
                            // 判断类上面是否含有注解@Bean
                            Bean annotation = clazz.getAnnotation(Bean.class); //获取到注解对象
                            if (annotation != null) { // 如果这个类的注解对象不为空 也就是说带有这个注解
                                // 获取clazz对象的构造并实例化
                                Object instance = clazz.getDeclaredConstructor().newInstance();
                                // 封装到map中
                                // 判断当前类是否有接口，如果存在就让第一个接口class作为map的key    PS:这里有个漏洞，如果一个接口被多个带有@Bean注解的类实现，那么就会出现key相同value覆盖的情况
                                if (clazz.getInterfaces().length > 0) {
                                    beanFactory.put(clazz.getInterfaces()[0], instance);
                                } else { // 如果没有就将自己的class对象作为key
                                    beanFactory.put(clazz, instance);
                                }
                            }
                        }
                    }

                }
            }
        }
    }

    private void loadDI() throws IllegalAccessException {
        // 实例化对象在beanFactoryMap集合里面
        // 1,遍历beanFactory的map集合
        Set<Map.Entry<Class, Object>> entries = beanFactory.entrySet();
        for (Map.Entry<Class, Object> entry: entries
             ) {
            // 2,获取map集合每个对象value 也就是得到beanFactory的中的类
            Object obj = entry.getValue();
            // 获取类的Class对象
            Class<?> clazz = obj.getClass();
            Field[] fields = clazz.getDeclaredFields();
            // 3,遍历得到的每个对象属性的数组,得到里面每个属性
            for (Field field : fields) {
                DI annotation = field.getAnnotation(DI.class);
                // 4,判断属性上是否带有@DI注解
                if (annotation != null) {
                    field.setAccessible(true); // 如果是私有属性,需要设置为外部可设置值
                    // 5,如果有@DI注解,就把对象进行注入
                    // field.getType()根据字段类型找到对象，比如UserService对象中添加了@DI注解的UserDao对象
                    // 而UserDaoImpl实现类本身又带有了@Bean注解，所以在最开始就已经以键值对的形式添加到BeanFactory中了，
                    field.set(obj,beanFactory.get(field.getType()));
                    System.out.println("属性注入成功!!!!!");
                }
            }
        }
    }

    //返回对象
    @Override
    public Object getBean(Class clazz) {
        return beanFactory.get(clazz);
    }

}
